/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004-2006  Benoit Blanchon                      */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/


/******************************************************************/
/* Build configuration                                            */
/******************************************************************/

#define TRACE_LEVEL	2	// warning level


/******************************************************************/
/* Includes                                                       */
/******************************************************************/

// module's interface
#include "ScanCache.h"

// project's headers
#include "Assert.h"
#include "Config.h"
#include "Strlcpy.h"
#include "Trace.h"

// standard headers
#include <tchar.h>


/******************************************************************/
/* Internal constants                                             */
/******************************************************************/

// ... empty ...


/******************************************************************/
/* Internal data structures                                       */
/******************************************************************/

typedef struct {
  LARGE_INTEGER	liScanTime ;
  LPTSTR	szFilePath ;
} ENTRY ;

typedef struct {

  ENTRY		*pBuffer ;
  UINT		nBufferLen ;	//!< max number of entries
 
  UINT		nEntryCount ;	//!< number of written entries
  UINT		nNextEntry ;	//!< index of the next entry to write

  HANDLE	hMutex ;

  HWND		hwnd ;

} INTERNAL_DATA ;


/******************************************************************/
/* Internal data                                                  */
/******************************************************************/

static INTERNAL_DATA	g_data ; 


/******************************************************************/
/* Internal functions                                             */
/******************************************************************/

// ... empty ...


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL ScanCache_Init ()
{
  UINT	nBufferSize ;

  TRACE ;

  // create mutex
  g_data.hMutex = CreateMutex (NULL, FALSE, NULL) ;

  // read config
  g_data.nBufferLen = Config_GetInteger(CFGINT_SCAN_CACHE_LENGTH) ;

  // alloc and clear buffer
  nBufferSize = g_data.nBufferLen * sizeof(ENTRY) ;
  g_data.pBuffer = (ENTRY*) malloc (nBufferSize) ;
  memset (g_data.pBuffer, 0, nBufferSize) ;
  
  // alloc failed ?
  if( ! g_data.pBuffer ) return FALSE ;

  // fill other fields
  g_data.nEntryCount	= 0 ;
  g_data.nNextEntry	= 0 ;
  g_data.hwnd		= NULL ;

  return TRUE ;  
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

VOID ScanCache_Uninit ()
{
  UINT	i ;

  TRACE ;

  for( i=0 ; i<g_data.nEntryCount ; i++ )
    free (g_data.pBuffer[i].szFilePath) ;

  free (g_data.pBuffer) ;

  // delete mutex
  CloseHandle (g_data.hMutex) ;

  // clear internal data
  memset (&g_data, 0, sizeof(g_data)) ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

VOID ScanCache_SetNotifyHwnd (HWND hwnd)
{
  g_data.hwnd = hwnd ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL ScanCache_AddTrustedFile (LPCTSTR		szFilePath, 
			       LARGE_INTEGER	*pliScanTime)
{
  ENTRY	*pCurEntry ;
  UINT	i ;
  UINT	nStrSize ;

  TRACE_INFO (TEXT("file=%s\n"), szFilePath) ;

  // verify params
  ASSERT (szFilePath!=NULL) ;
  ASSERT (pliScanTime!=NULL) ;

  // lock mutex
  WaitForSingleObject (g_data.hMutex, INFINITE) ;
  
  // no buffer => skip
  if( g_data.nBufferLen==0 ) return FALSE ;

  //
  // 1. Look for a matching file name
  //    Change scan time if found.
  //

  // -------- for each entry (begin) --------
  for( i=0 ; i<g_data.nEntryCount ; i++ )
    {
      pCurEntry	= &g_data.pBuffer[i] ;
      
      // file name matches ?
      if( ! _tcsicmp(szFilePath,pCurEntry->szFilePath) )
	{
	  TRACE_INFO (TEXT("File already in cache\n")) ;
	  pCurEntry->liScanTime = *pliScanTime ;

	  // unlock mutex
	  ReleaseMutex (g_data.hMutex) ;

	  if( g_data.hwnd )
	    PostMessage (g_data.hwnd, WM_SCANCACHENOTIFY, SCN_FILEUPDATED, i) ;

	  return TRUE ;
	}
    }
  // -------- for each entry (end) --------


  //
  // 2. No matching file name.
  //    Add a new entry.
  //

  TRACE_INFO (TEXT("File not in cache\n")) ;

  i = g_data.nNextEntry++ ;

  if( g_data.nNextEntry >= g_data.nBufferLen )
    g_data.nNextEntry = 0 ;

  if( g_data.nEntryCount < g_data.nBufferLen )
    g_data.nEntryCount++ ;

  pCurEntry = &g_data.pBuffer[i] ;
  
  // replace file path string
  nStrSize = ( _tcslen(szFilePath) + 1 ) * sizeof(TCHAR) ;
  free (pCurEntry->szFilePath) ;
  pCurEntry->szFilePath = (LPTSTR) malloc (nStrSize) ;
  memcpy (pCurEntry->szFilePath, szFilePath, nStrSize) ;

  // copy scan time
  pCurEntry->liScanTime = *pliScanTime ;

  // unlock mutex
  ReleaseMutex (g_data.hMutex) ;

  if( g_data.hwnd )
    PostMessage (g_data.hwnd, WM_SCANCACHENOTIFY, SCN_FILEADDED, i) ;

  return TRUE ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL ScanCache_IsFileTrusted (LPCTSTR		szFilePath,
			      LARGE_INTEGER	*pliFileTime) 
{
  ENTRY	*pCurEntry ;
  UINT	i ;

  TRACE_INFO (TEXT("file=%s\n"), szFilePath) ;

  // lock mutex
  WaitForSingleObject (g_data.hMutex, INFINITE) ;

  // -------- for each entry (begin) --------
  for( i=0 ; i<g_data.nEntryCount ; i++ )
    {
      pCurEntry	= &g_data.pBuffer[i] ;
      
      // file name matches ?
      if( ! _tcsicmp(szFilePath,pCurEntry->szFilePath) )
	{
	  BOOL	bTrusted = TRUE ;
	  
	  if( pliFileTime != NULL )
	    {
#if TRACE_LEVEL > 2
	      SYSTEMTIME stFileTime, stScanTime ;
	    
	      FileTimeToSystemTime ((FILETIME*)pliFileTime, &stFileTime) ;
	      FileTimeToSystemTime ((FILETIME*)&pCurEntry->liScanTime, &stScanTime) ;
	      
	      TRACE_INFO (TEXT("File time = %02dh%02dm%02ds %04d/%02d/%02d\n"),
			  stFileTime.wHour, stFileTime.wMinute, stFileTime.wSecond,
			  stFileTime.wYear, stFileTime.wMonth, stFileTime.wDay) ;
	      
	      TRACE_INFO (TEXT("Scan time = %02dh%02dm%02ds %04d/%02d/%02d\n"),
			  stScanTime.wHour, stScanTime.wMinute, stScanTime.wSecond,
			  stScanTime.wYear, stScanTime.wMonth, stScanTime.wDay) ;
#endif

	      bTrusted = pliFileTime->QuadPart < pCurEntry->liScanTime.QuadPart ;
	    }

	  // unlock mutex
	  ReleaseMutex (g_data.hMutex) ;

	  // file can be considered as trusted
	  // if it is older than the last scan time
	  return bTrusted ;
	}
    }
  // -------- for each entry (end) --------

  // unlock mutex
  ReleaseMutex (g_data.hMutex) ;

  // file not found in cache
  // it is considered as unsafe
  return FALSE ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL ScanCache_EnumTrustedFiles (ENUMTRUSTEDFILESCB pfnCallback, LPVOID pContext) 
{
  UINT	i ;

  // lock mutex
  WaitForSingleObject (g_data.hMutex, INFINITE) ;

  TRACE_INFO (TEXT("nEntryCount = %d\n"), g_data.nEntryCount) ;

  for( i=0 ; i<g_data.nEntryCount ; i++ )
    {
      TRACE_INFO (TEXT("%s\n"), g_data.pBuffer[i].szFilePath) ; 

      pfnCallback (pContext, 
		   i,
		   g_data.pBuffer[i].szFilePath,
		   &g_data.pBuffer[i].liScanTime) ;
    }

  // unlock mutex
  ReleaseMutex (g_data.hMutex) ;
      
  return TRUE ;
}


/******************************************************************/
/* Exported function                                              */
/******************************************************************/

BOOL ScanCache_GetTrustedFile (UINT iNumber,
			       LPTSTR szFilePath, UINT nMaxFilePath,
			       LARGE_INTEGER *pliScanTime) 
{
  // lock mutex
  WaitForSingleObject (g_data.hMutex, INFINITE) ;
  
  if( iNumber >= g_data.nEntryCount ) {
    ReleaseMutex (g_data.hMutex) ;
    return FALSE ;
  }

  _tcslcpy (szFilePath, g_data.pBuffer[iNumber].szFilePath, nMaxFilePath) ;
  *pliScanTime = g_data.pBuffer[iNumber].liScanTime ;

  // unlock mutex
  ReleaseMutex (g_data.hMutex) ;
      
  return TRUE ;
}
